package com.bj58.ecdata.dbconnectionpool;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConnectionPool
{
  private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);
  private Stack<Connection> AvailableConn = new Stack<Connection>();
  private DBConfig _Config;
  private List<Connection> Pool = new ArrayList<Connection>();
  
  public ConnectionPool() {}
  
  public ConnectionPool(DBConfig config)
  {
    try
    {
      this._Config = config;
      LoadDrivers();
      Init();
      RegisterExcetEven();
    }
    catch (Exception err)
    {
      err.printStackTrace();
    }
  }
  
  public int GetAllCount()
  {
    return this.Pool.size();
  }
  
  public int GetFreeConnCount()
  {
    return this.AvailableConn.size();
  }
  
  private PoolState state = new PoolState();
  
  public synchronized Connection Get()
    throws Exception
  {
    int freeCount = this.AvailableConn.size();
    this.state.setNoWorkCout(freeCount);
    if (freeCount > 0)
    {
      Connection conn = (Connection)this.AvailableConn.pop();
      if (conn == null) {
        this.Pool.remove(conn);
      }
      logger.debug("Connection get " + conn + " connection size is " + GetAllCount() + " FreeConnCount is " + GetFreeConnCount());
      return conn;
    }
    if (GetAllCount() < this._Config.getMaxPoolSize())
    {
      Connection conn = CreateConn();
      logger.debug(" Connection get " + conn + " connection size is " + GetAllCount() + " FreeConnCount is " + GetFreeConnCount());
      return conn;
    }
    throw new Exception("db connection pool is full");
  }
  
  public Connection GetReadConnection()
    throws Exception
  {
    return Get();
  }
  
  public synchronized void Release(Connection conn)
  {
    if (conn != null) {
      try
      {
        if ((this._Config.getAutoShrink().booleanValue()) && 
          (this.state.GetNoWorkCount(this._Config.getIdleTimeout() * 1000) > 0))
        {
          Destroy(conn);
          this.state.setNoWorkCout(GetFreeConnCount());
        }
        else if (!conn.isClosed())
        {
          this.AvailableConn.push(conn);
        }
        else
        {
          Destroy(conn);
        }
      }
      catch (SQLException e)
      {
        e.printStackTrace();
      }
    }
    logger.debug("Release method connection size is " + GetAllCount() + " FreeConnCount is " + GetFreeConnCount());
  }
  
  private synchronized void Destroy(Connection conn)throws SQLException
	{
	  if(conn == null)
	     return;
	  conn.close();
	  Pool.remove(conn);
	  logger.debug((new StringBuilder(" close one connection!!!")).append(conn).append(" connection size is ").append(GetAllCount()).append(" FreeConnCount is ").append(GetFreeConnCount()).toString());
	  conn = null;
	
	}
  
  private synchronized Connection CreateConn()
  throws Exception
	{
	  Connection conn = null;
	  try
	  {
	      if(_Config.getUserName() == null)
	          conn = DriverManager.getConnection(_Config.getConnetionUrl());
	      else
	          conn = DriverManager.getConnection(_Config.getConnetionUrl(), _Config.getUserName(), _Config.getPassWord());
	  }
	  catch(SQLException e)
	  {
	      throw e;
	  }
	  if(conn != null && !conn.isClosed())
	  {
	      logger.debug((new StringBuilder(" this conn is create ")).append(conn).append(" connection size is ").append(GetAllCount()).append(" FreeConnCount is ").append(GetFreeConnCount()).toString());
	      Pool.add(conn);
	  }
	  return conn;
	}
	  
	  private void LoadDrivers()
	  {
	    try
	    {
	      Driver driver = (Driver)Class.forName(this._Config.getDriversClass()).newInstance();
	      DriverManager.registerDriver(driver);
	      DriverManager.setLoginTimeout(1);
	    }
	    catch (Exception e)
	    {
	      e.printStackTrace();
	    }
	  }
  
  private void Init()throws Exception{
    for (int i = 0; i < this._Config.getMinPoolSize(); i++){
      Connection conn = CreateConn();
      if (conn != null) {
        this.AvailableConn.push(conn);
      }
    }
  }
  
  private void RegisterExcetEven()
  {
    Runtime.getRuntime().addShutdownHook(new Thread()
    {
      public void run()
      {
        System.out.println("application exiting,begin remove all connections.");
        for (Connection conn : ConnectionPool.this.Pool) {
          try
          {
            if ((conn != null) && (!conn.isClosed())) {
              conn.close();
            }
          }
          catch (SQLException e)
          {
            e.printStackTrace();
          }
        }
      }
    });
  }
  
  public class PoolState
  {
    private long duration;
    private int noWorkCount = 0;
    
    public PoolState() {}
    
    public synchronized void setNoWorkCout(int noWorkCout)
    {
      if ((this.noWorkCount <= 2) && (noWorkCout > 2)) {
        this.duration = System.currentTimeMillis();
      }
      this.noWorkCount = noWorkCout;
    }
    
    public synchronized int GetNoWorkCount(long duration)
    {
      if (System.currentTimeMillis() - this.duration > duration) {
        return this.noWorkCount - 2;
      }
      return 0;
    }
  }
}
